/**
 * 
 */
package za.co.sindi.oauth.core.service;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

import za.co.sindi.oauth.core.context.RequestContext;
import za.co.sindi.oauth.core.context.ResponseContext;
import za.co.sindi.oauth.core.result.Result;
import za.co.sindi.oauth.exception.ServiceException;
import za.co.sindi.oauth.server.config.RequestConfig;

/**
 * This class finds methods reflectively and invokes it.
 * 
 * @author Buhake Sindi
 * @since 14 April 2012
 *
 */
public abstract class InvokeableService implements Service {

//	protected static final Class<?>[] PARAMETER_TYPES = { RequestContext.class };
	protected final Logger logger = Logger.getLogger(this.getClass());
	protected Class<?> actualClass = this.getClass();
	protected Map<String, Method> methods = new HashMap<String, Method>();
	
	/* (non-Javadoc)
	 * @see com.neurologic.oauth.core.service.Service#service(com.neurologic.oauth.server.config.RequestConfig, com.neurologic.oauth.core.context.RequestContext, com.neurologic.oauth.core.context.ResponseContext)
	 */
	@Override
	public Result service(RequestConfig requestConfig, RequestContext request, ResponseContext response) throws ServiceException {
		// TODO Auto-generated method stub
		if (requestConfig == null) {
			throw new IllegalArgumentException("A RequestConfig cannot be null.");
		}
		
		Result result = null;
		try {
			//Execute
			Method method = getMethod(requestConfig.getExecute() /*, PARAMETER_TYPES*/);
			if (method == null) {
				throw new ServiceException("Cannot find method " + requestConfig.getExecute() + "(with context of sub interface " + RequestContext.class.getName() + ").");
			}
			
			Object instance = this;
			if (!this.getClass().isAssignableFrom(actualClass)) {
				instance = actualClass.newInstance();
			}
			
			if (logger.isInfoEnabled()) {
				logger.info("Executing " + actualClass.getName() + "." + method.getName()  + "(" + request.getClass().getName() + ").");
			}
			
			Object value = method.invoke(instance, new Object[] { request});
			if (value != null && !(value instanceof Result)) {
				throw new ServiceException(actualClass.getName() + "." + method.getName() + " returns a type of " + value.getClass().getName() + " which isn't an instance of " + Result.class.getName());
			}
			
			result = (Result) value;
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			throw new ServiceException("service()", e);
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			throw new ServiceException("service()", e);
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			throw new ServiceException("service()", e);
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			throw new ServiceException("service()", e);
		}
		
		return result;
	}

	protected Method getMethod(String name /*, Class<?>[] types*/) {
		synchronized (methods) {
			Method method = methods.get(name);;
			if (method == null) {
				for (Method _method : actualClass.getMethods()) {
					if (_method.getName().equals(name)) {
						Class<?> parameters[] = _method.getParameterTypes();
						if (parameters != null && parameters.length == 1 && RequestContext.class.isAssignableFrom(parameters[0])) {
							method = _method;
							break;
						}
					}
				}
				
//				method = actualClass.getMethod(name, types);
				if (method != null) {
					//Add to cache
					methods.put(name, method);
				}
			}
					
			return method;
		}
	}
}
